﻿package {
	
	/**
	 * ...
	 * @author maximov
	 */
	import flash.display.Scene;
	import flash.display.SpreadMethod;
	import flash.display.Sprite;
	import flash.display.BlendMode;
	import flash.display.Bitmap;

	import flash.media.Camera;
	import flash.media.Video;

	import flash.events.Event;
	import flash.events.MouseEvent;

	import flash.text.TextField;
	import flash.text.TextFormat;

	import flash.geom.Vector3D;


	//for 3d engine.	
	import org.papervision3d.core.math.Matrix3D;
	import org.papervision3d.render.LazyRenderEngine;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.view.Viewport3D;
	import org.papervision3d.core.math.Number3D;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.WireframeMaterial;
	import org.papervision3d.materials.BitmapMaterial;
	import org.papervision3d.materials.shadematerials.GouraudMaterial;
	import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
	import org.papervision3d.materials.utils.MaterialsList;
	import org.papervision3d.objects.primitives.Cube;
	import org.papervision3d.objects.primitives.Plane;

	//for physics
	import jiglib.physics.RigidBody;
	import jiglib.plugin.papervision3d.Papervision3DPhysics;




	[SWF(width=640, height=480, backgroundColor=0x808080, frameRate=60)]
	public class Main extends Sprite {
		// the facade for FLARToolkit
		public var ref_width:Number = 640;
		public var ref_height:Number = 480;
		
		private var flar:Flar;

		//to flame...
		private var label:TextField;

		//scene for 3d objects
		private var scene:Scene3D;

		//container for all materials & textures
		private var Materials:Object;

		//ball texture
		[Embed (source="assets/texture.png", mimeType="image/png")]
		private var ball_texture:Class;

		// scenegraph & physics system
		private var renderer:LazyRenderEngine;
		private var physics:Papervision3DPhysics;

		//maze controller
		private var dragger:Dragger;
		//the ball 
		private var ball:RigidBody;
		
		//win count
		private var hittimes:int = 0;
	 	private	var correct_axis:Matrix3D= Matrix3D.rotationX(3.1415);;
			
		public function Main() {
			label = createTextField(20, 20, ref_width-20, ref_height-20);
			//create FLARToolkit facade 
			try
			{
				flar = new Flar(ref_width,ref_height);
			}
			catch (error:Error)
			{
				set_error_text(error.message);
			}
			addChild(flar.layer);
			flar.scale_to(ref_width,ref_height);

			//init 3d & physics
			init_paper();
			init_jig();
			addChild(label);
			
			
			
			set_welcome_text();
			
			//add all objects to scene
			create_objects();
			
			addEventListener(Event.ENTER_FRAME, EnterFrame);
			addEventListener(MouseEvent.CLICK, ResetBall);

		}
		private function set_error_text(message:String):void
		{
			label.y = this.height / 2;
			var style:TextFormat = new TextFormat();
			style.size = 30;
			style.color = 0xff0000;
			label.text = message;
			label.setTextFormat(style);
		}
		private function set_welcome_text():void
		{
			var style:TextFormat = new TextFormat();
			style.size = 16;
			style.color = 0xffffff;
			label.text = "Trace ball to the red cube. Gravity is alligned inside the screen. Click to reset ball.";
			label.setTextFormat(style);	
		}

		// pv3d engine initialization
		private function init_paper():void {
			//the layer for 3d draw.
			var pap_layer:Sprite = new Sprite();
			addChild(pap_layer);
			//make it work like a mirror
			pap_layer.scaleX = -1;
			pap_layer.x = ref_width;
			var viewport:Viewport3D = pap_layer.addChild(new Viewport3D(width, height)) as Viewport3D;
			scene = new Scene3D();
			renderer = new LazyRenderEngine(scene, flar.camera3d, viewport);
			
			//add to the scene the container following the detected position
			scene.addChild(flar.MarkerNode);
			
			//create the marker for detected position
			var mat:WireframeMaterial = new WireframeMaterial(0xff0000, 1, 2); // with wireframe.
			var plane:Plane = new Plane(mat, 80, 80); // 80mm x 80mm。
			plane.rotationY = 180;
			flar.MarkerNode.addChild(plane);

			//create materials. nothing interesting here.
			var light:PointLight3D= new PointLight3D();
			light.x = -1000;
			light.y = -800;
			light.z = -100;
			Materials = new Object();
			var wall_mat:GouraudMaterial = new GouraudMaterial(light, 0x22ff00, 0x10754e);
			var bitmap:Bitmap = new ball_texture();
			var ball_mat:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false);
			var finish_mat:GouraudMaterial= new GouraudMaterial(light, 0xff2200, 0x75104e)
			Materials.ball   = ball_mat;
			Materials.finish ={ all:finish_mat };
			Materials.walls = { all:wall_mat };

			
		}
		//physics system initialization
		private function init_jig():void
		{
			physics = new Papervision3DPhysics(scene, 1);
			physics.set_solve("NORMAL");
			
			//set the gravity inside the deep of the screen. It's appropriate for laptop fixed webcam.
			physics.engine.setGravity(new Vector3D(0, 0, 80));
		}
		//create maze and balll
		private function create_objects():void
		{
			dragger = new Dragger();
			var maze:Array = Maze.CreateMaze(physics, Materials.walls);
			dragger.addarray(maze);
 
			var finishpoint:Cube = new Cube(new MaterialsList(Materials.finish),
			Maze.factor, Maze.factor, Maze.factor);
			finishpoint.position = num3d(Maze.finishpos);
			flar.MarkerNode.addChild(finishpoint);
			
			ball = physics.createSphere(Materials.ball, Maze.factor);
			physics.getMesh(ball).culled = true;
			ball.z = -100;
			ball.setVelocity(new Vector3D(0, 0, 1) );
			ball.mass = 30;
		   
		}
		//put ball to the beginning of the maze
		private function ResetBall(e:Event = null):void
		{
			var pos:Number3D=num3d(Maze.startpos)
			Matrix3D.multiplyVector(flar.MarkerNode.transform, pos);
			pos.z -= Maze.factor * 4;
			ball.moveTo(vec3d(pos));
			ball.setVelocity(new Vector3D(0, -1, 0) );
		}
		//nuff said...
		private function EnterFrame(e:Event = null):void
		{
			flar.get_capture();
			if (flar.detected) {
				 
				flar.MarkerNode.transform=Matrix3D.multiply(flar.MarkerNode.transform,correct_axis)
				dragger.transform(flar.MarkerNode.transform);


			}
			physics.step();
			renderer.render();
			
			if (test_finish())
			{
				win();
				ResetBall();
			}
			if (test_out())
			{
				set_welcome_text();
				ResetBall();
			}
		}
		//We have a winner!!!
		private function win():void
		{
			hittimes++;
			var style:TextFormat = new TextFormat();
			style.size = 25;
			style.color = 0xff1111;
			label.text = "Got it "+hittimes.toString()+" times!";
			label.setTextFormat(style);
		}
		//test if the ball hit the finish target
		private function test_finish():Boolean
		{
			var finishpos:Number3D = num3d(Maze.finishpos);
			Matrix3D.multiplyVector(flar.MarkerNode.transform, finishpos);
			var dist:Vector3D = ball.currentState.position.subtract(vec3d(finishpos));
			return (dist.length < Maze.factor * 2);
		}
		//check if the ball is out
		private function test_out():Boolean
		{
			return ball.currentState.position.length > 600 * Maze.factor;
		}
		//syntax sugar
		private function createTextField(x:Number, y:Number, width:Number, height:Number):TextField
		{
			var result:TextField = new TextField();
			result.x = x; result.y = y;
			result.width = width; result.height = height;
			addChild(result);
			return result;
		}
		//syntax sugar for converting pv3d<->jiglib vectors.
		private function vec3d( a:Number3D):Vector3D
		{
			return new Vector3D(a.x, a.y, a.z);
		}
		private function num3d( a:Vector3D):Number3D
		{
			return new Number3D(a.x, a.y, a.z);
		}
	}
}